#include <stdio.h>
#include <stdlib.h>
#include "nyu_tree.h"

extern NyuScope *current_scope;

static gboolean is_initialized = FALSE;
static NyuVarList *all_var_list;
static GHashTable *all_var_hash;

NyuTreeList *nyu_tree_list_prepend(NyuTreeList *self, NyuTree *value)
{
    NyuTreeList *node;

    node = (NyuTreeList *)malloc(sizeof(NyuTreeList));
    if (!node) {
        perror("cannot alloc NyuTreeList!!");
        abort();
    }

    node->base.type = NYU_TREE_TYPE_LIST;
    node->value = value;
    node->next = self;

    return node;
}

NyuTreeList *nyu_tree_list_reverse(NyuTreeList *self)
{
    NyuTreeList *l, *t, *p = NULL;

    for (l = self; l; l = t) {
        t = l->next;
        l->next = p;
        p = l;
    }

    return p;
}

NyuTree *nyu_tree_expr_new(NyuTreeType type,
                           NyuTree *lnode,
                           NyuTree *rnode)
{
    NyuTreeExpr *self;

    self = (NyuTreeExpr *)malloc(sizeof(NyuTreeExpr));
    if (!self) {
        perror("cannot alloc NyuTreeExpr!!");
        abort();
    }

    self->base.type = type;
    self->lnode = lnode;
    self->rnode = rnode;

    return NYU_TREE(self);
}

NyuTree *nyu_tree_cond_new(NyuTree *cond,
                           NyuTree *tnode,
                           NyuTree *fnode)
{
    NyuTreeCond *self;

    self = (NyuTreeCond *)malloc(sizeof(NyuTreeCond));
    if (!self) {
        perror("cannot alloc NyuTreeCond!!");
        abort();
    }

    self->base.type = NYU_TREE_TYPE_EXPR_COND;
    self->cond = cond;
    self->tnode = tnode;
    self->fnode = fnode;

    return NYU_TREE(self);
}

NyuTree *nyu_tree_call_new(NyuTree *prim, NyuTreeList *args)
{
    NyuTreeCall *self;
    NyuTreeList *l;
    long i;

    self = (NyuTreeCall *)malloc(sizeof(NyuTreeCall));
    if (!self) {
        perror("cannot alloc NyuTreeCall!!");
        abort();
    }

    self->base.type = NYU_TREE_TYPE_CALL;
    self->prim = prim;
    self->args = args;

    for (l = args, i = 0; l; l = l->next, i++) {
    }
    self->argc = i;

    if (self->argc >= NYU_MAX_ARGLEN) {
        fprintf(stderr, "Sorry arguments must be < %d\n", NYU_MAX_ARGLEN);
        abort();
    }

    return NYU_TREE(self);
}

NyuTree *nyu_tree_slot_access_new(NyuTree *prim, char *name)
{
    NyuTreeSlotAccess *self;

    self = (NyuTreeSlotAccess *)malloc(sizeof(NyuTreeSlotAccess));
    if (!self) {
        perror("cannot alloc NyuTreeSlotAccess!!");
        abort();
    }

    self->base.type = NYU_TREE_TYPE_SLOT_ACCESS;
    self->prim = prim;
    self->name = nyu_string_new(name);

    return NYU_TREE(self);
}

NyuTree *nyu_tree_block_new(NyuTreeList *stmts)
{
    NyuTreeBlock *self;

    self = (NyuTreeBlock *)malloc(sizeof(NyuTreeBlock));
    if (!self) {
        perror("cannot alloc NyuTreeBlock!!");
        abort();
    }

    self->base.type = NYU_TREE_TYPE_BLOCK;
    self->stmts = stmts;

    return NYU_TREE(self);
}

NyuTree *nyu_tree_object_new(NyuTreeList *slots)
{
    NyuTreeObject *self;

    self = (NyuTreeObject *)malloc(sizeof(NyuTreeObject));
    if (!self) {
        perror("cannot alloc NyuTreeObject!!");
        abort();
    }

    self->base.type = NYU_TREE_TYPE_OBJECT;
    self->slots = slots;

    return NYU_TREE(self);
}

NyuTree *nyu_tree_object_slot_new(char *name, NyuTree *expr)
{
    NyuTreeObjectSlot *self;

    self = (NyuTreeObjectSlot *)malloc(sizeof(NyuTreeObjectSlot));
    if (!self) {
        perror("cannot alloc NyuTreeObjectSlot!!");
        abort();
    }

    self->base.type = NYU_TREE_TYPE_OBJECT_SLOT;
    self->name = nyu_string_new(name);
    self->expr = expr;

    return NYU_TREE(self);
}

NyuTree *nyu_tree_array_new(NyuTreeList *elms)
{
    NyuTreeArray *self;
    NyuTreeList *l;
    long i;

    self = (NyuTreeArray *)malloc(sizeof(NyuTreeArray));
    if (!self) {
        perror("cannot alloc NyuTreeArray!!");
        abort();
    }

    self->base.type = NYU_TREE_TYPE_ARRAY;
    self->elms = elms;

    for (l = elms, i = 0; l; l = l->next, i++) {
    }
    self->len = i;

    return NYU_TREE(self);
}

NyuTree *nyu_tree_range_new(NyuTree *from,
                            NyuTree *to,
                            NyuTree *step,
                            int is_include_border)
{
    NyuTreeRange *self;

    self = (NyuTreeRange *)malloc(sizeof(NyuTreeRange));
    if (!self) {
        perror("cannot alloc NyuTreeRange!!");
        abort();
    }

    self->base.type = NYU_TREE_TYPE_RANGE;
    self->from = from;
    self->to = to;
    self->step = step;
    self->is_include_border = is_include_border;

    return NYU_TREE(self);
}

NyuTree *nyu_tree_symbol_new(char *symbol)
{
    NyuTreeSymbol *self;
    NyuVarList *l = NULL;

    self = (NyuTreeSymbol *)malloc(sizeof(NyuTreeSymbol));
    if (!self) {
        perror("cannot alloc NyuTreeSymbol!!");
        abort();
    }

    self->base.type = NYU_TREE_TYPE_SYMBOL;
    self->symbol = symbol;

    /* ここで名前テーブルを引いておく */
    //l = nyu_var_list_find(all_var_list, symbol);
    l = g_hash_table_lookup(all_var_hash, symbol);
    if (!l) {
        l = all_var_list = nyu_var_list_prepend(all_var_list, symbol, 0);
        g_hash_table_insert(all_var_hash, symbol, l);
    }
    self->ref = &l->data;

    return NYU_TREE(self);
}

NyuTree *nyu_tree_intval_new(long intval)
{
    NyuTreeIntval *self;

    self = (NyuTreeIntval *)malloc(sizeof(NyuTreeIntval));
    if (!self) {
        perror("cannot alloc NyuTreeIntval!!");
        abort();
    }

    self->base.type = NYU_TREE_TYPE_INTVAL;
    self->intval = intval;

    return NYU_TREE(self);
}

NyuTree *nyu_tree_fltval_new(double fltval)
{
    NyuTreeFltval *self;

    self = (NyuTreeFltval *)malloc(sizeof(NyuTreeFltval));
    if (!self) {
        perror("cannot alloc NyuTreeFltval!!");
        abort();
    }

    self->base.type = NYU_TREE_TYPE_INTVAL;
    self->fltval = fltval;

    return NYU_TREE(self);
}

NyuTree *nyu_tree_strval_new(char *strval)
{
    NyuTreeStrval *self;

    self = (NyuTreeStrval *)malloc(sizeof(NyuTreeStrval));
    if (!self) {
        perror("cannot alloc NyuTreeStrval!!");
        abort();
    }

    self->base.type = NYU_TREE_TYPE_STRVAL;
    self->strval = strval;
    self->strobj = nyu_string_new(strval);

    return NYU_TREE(self);
}

NyuTree *nyu_tree_argref_new(long idx)
{
    NyuTreeArgref *self;

    self = (NyuTreeArgref *)malloc(sizeof(NyuTreeArgref));
    if (!self) {
        perror("cannot alloc NyuTreeArgref!!");
        abort();
    }

    self->base.type = NYU_TREE_TYPE_ARGREF;
    self->idx = idx;

    /*
    if (idx == 0) {
        fprintf(stderr, "Sorry $0 is not supported currently\n");
        abort();
    }
    */

    if (idx >= NYU_MAX_ARGLEN) {
        fprintf(stderr, "Sorry argument ref must be < %d\n", NYU_MAX_ARGLEN);
        abort();
    }

    return NYU_TREE(self);
}

NyuTree *nyu_tree_arglen_new()
{
    NyuTreeArglen *self;

    self = (NyuTreeArglen *)malloc(sizeof(NyuTreeArglen));
    if (!self) {
        perror("cannot alloc NyuTreeArglen!!");
        abort();
    }

    self->base.type = NYU_TREE_TYPE_ARGLEN;

    return NYU_TREE(self);
}

NyuTree *nyu_tree_curblk_new()
{
    NyuTreeCurblk *self;

    self = (NyuTreeCurblk *)malloc(sizeof(NyuTreeCurblk));
    if (!self) {
        perror("cannot alloc NyuTreeCurblk!!");
        abort();
    }

    self->base.type = NYU_TREE_TYPE_CURBLK;

    return NYU_TREE(self);
}

static void rec_nyu_tree_dump(NyuTree *self, int depth)
{
    int i;

    if (!self)
        return;

    for (i = 0; i < depth; i++) {
        putchar(' ');
    }

    switch (self->type) {
    case NYU_TREE_TYPE_LIST:
        {
            NyuTreeList *l = NYU_TREE_LIST(self);

            printf("- LIST(%p)\n", l);

            for (; l; l = l->next) {
                rec_nyu_tree_dump(l->value, depth + 1);
            }
        }
        break;

    case NYU_TREE_TYPE_EXPR_PUT:
        printf("- PUT(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_SET:
        printf("- SET(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_ADD_ASSIGN:
        printf("- ADD_ASSIGN(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_SUB_ASSIGN:
        printf("- SUB_ASSIGN(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_MUL_ASSIGN:
        printf("- MUL_ASSIGN(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_DIV_ASSIGN:
        printf("- DIV_ASSIGN(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_MOD_ASSIGN:
        printf("- MOD_ASSIGN(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_LSHIFT_ASSIGN:
        printf("- LSHIFT_ASSIGN(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_RSHIFT_ASSIGN:
        printf("- RSHIFT_ASSIGN(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_BAND_ASSIGN:
        printf("- BAND_ASSIGN(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_BXOR_ASSIGN:
        printf("- BXOR_ASSIGN(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_BOR_ASSIGN:
        printf("- BOR_ASSIGN(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_LAND_ASSIGN:
        printf("- LAND_ASSIGN(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_LOR_ASSIGN:
        printf("- LOR_ASSIGN(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_SWAP:
        printf("- SWAP(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_COND:
        printf("- COND(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_LOR:
        printf("- LOR(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_LXOR:
        printf("- LXOR(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_LAND:
        printf("- LAND(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_BOR:
        printf("- BOR(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_BXOR:
        printf("- BXOR(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_BAND:
        printf("- BAND(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_EQ:
        printf("- EQ(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_NE:
        printf("- NE(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_EQ3:
        printf("- EQ3(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_NE3:
        printf("- NE3(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_LT:
        printf("- LT(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_LE:
        printf("- LE(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_GT:
        printf("- GT(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_GE:
        printf("- GE(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_CMP:
        printf("- CMP(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_OWN_PROTO:
        printf("- OWN_PROTO(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_OWN_MEMBER:
        printf("- OWN_MEMBER(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_LSHIFT:
        printf("- LSHIFT(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_RSHIFT:
        printf("- RSHIFT(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_ADD:
        printf("- ADD(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_SUB:
        printf("- SUB(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_MUL:
        printf("- MUL(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_DIV:
        printf("- DIV(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_MOD:
        printf("- MOD(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_LNOT:
        printf("- LNOT(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_BNOT:
        printf("- BNOT(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_NEGAT:
        printf("- NEGAT(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_INC:
        printf("- INC(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_DEC:
        printf("- DEC(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_POST_INC:
        printf("- POST_INC(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_POST_DEC:
        printf("- POST_DEC(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_EXPR_SLOT_ACCESS:
        printf("- SLOT_ACCESS(%p)\n", self);
        goto expr_dump;

    case NYU_TREE_TYPE_SLOT_ACCESS:
        printf("- SLOT_ACCESS2(%p): name=%s\n", self,
               NYU_TREE_SLOT_ACCESS(self)->name->str);
        rec_nyu_tree_dump(NYU_TREE_SLOT_ACCESS(self)->prim, depth + 1);
        break;

    case NYU_TREE_TYPE_CALL:
        printf("- CALL(%p)\n", self);
        rec_nyu_tree_dump(NYU_TREE_CALL(self)->prim, depth + 1);
        {
            NyuTreeList *l = NYU_TREE_CALL(self)->args;

            for (; l; l = l->next) {
                rec_nyu_tree_dump(l->value, depth + 1);
            }
        }
        break;

    case NYU_TREE_TYPE_BLOCK:
        printf("- BLOCK(%p)\n", self);
        {
            NyuTreeList *l = NYU_TREE_BLOCK(self)->stmts;

            for (; l; l = l->next) {
                rec_nyu_tree_dump(l->value, depth + 1);
            }
        }
        break;

    case NYU_TREE_TYPE_OBJECT:
        printf("- OBJECT(%p)\n", self);
        {
            NyuTreeList *l = NYU_TREE_OBJECT(self)->slots;

            for (; l; l = l->next) {
                rec_nyu_tree_dump(l->value, depth + 1);
            }
        }
        break;

    case NYU_TREE_TYPE_OBJECT_SLOT:
        printf("- OBJECT_SLOT(%p): %s\n", self,
               NYU_TREE_OBJECT_SLOT(self)->name->str);
        rec_nyu_tree_dump(NYU_TREE_OBJECT_SLOT(self)->expr, depth + 1);
        break;

    case NYU_TREE_TYPE_ARRAY:
        printf("- ARRAY(%p)\n", self);
        {
            NyuTreeList *l = NYU_TREE_ARRAY(self)->elms;

            for (; l; l = l->next) {
                rec_nyu_tree_dump(l->value, depth + 1);
            }
        }
        break;

    case NYU_TREE_TYPE_RANGE:
        printf("- RANGE(%p): is_include_border=%d\n", self,
               NYU_TREE_RANGE(self)->is_include_border);
        rec_nyu_tree_dump(NYU_TREE_RANGE(self)->from, depth + 1);
        rec_nyu_tree_dump(NYU_TREE_RANGE(self)->to, depth + 1);
        rec_nyu_tree_dump(NYU_TREE_RANGE(self)->step, depth + 1);
        break;

    case NYU_TREE_TYPE_SYMBOL:
        printf("- SYMBOL(%p): %s\n", self, NYU_TREE_SYMBOL(self)->symbol);
        break;

    case NYU_TREE_TYPE_STRVAL:
        printf("- STRVAL(%p): %s\n", self, NYU_TREE_STRVAL(self)->strval);
        break;

    case NYU_TREE_TYPE_INTVAL:
        printf("- INTVAL(%p): %ld\n", self, NYU_TREE_INTVAL(self)->intval);
        break;

    case NYU_TREE_TYPE_FLTVAL:
        printf("- FLTVAL(%p): %g\n", self, NYU_TREE_FLTVAL(self)->fltval);
        break;

    expr_dump:
        rec_nyu_tree_dump(NYU_TREE_EXPR(self)->lnode, depth + 1);
        rec_nyu_tree_dump(NYU_TREE_EXPR(self)->rnode, depth + 1);
        break;

    default:
        printf("UNKNOWN(%p): type=%d\n", self, self->type);
        break;
    }
}

void nyu_tree_dump(NyuTree *self)
{
    rec_nyu_tree_dump(self, 0);
}

/* -------------------------------------------------------------------------- */

#include "nyu_array.h"
#include "nyu_range.h"

void nyu_initialize()
{
    if (is_initialized)
        return;

    all_var_hash = g_hash_table_new(g_str_hash, g_str_equal);

    strval_proto = nyu_string_new("__proto__");
    root_object = nyu_object_new();
    root_object_val = ((long)root_object | 2);

    nyu_array_initialize();
    nyu_range_initialize();
}

void nyu_finalize()
{
    if (!is_initialized)
        return;

    nyu_refcnt_unref(root_object);

    g_hash_table_unref(all_var_hash);
    all_var_hash = NULL;

    nyu_range_finalize();
    nyu_array_finalize();

    is_initialized = FALSE;
}

void nyu_add_func(char *name, NyuFunc func)
{
    all_var_list = nyu_var_list_prepend(
        all_var_list, name,
        nyu_var_data_new(NULL, ((long)func | 0x1), current_scope));
    g_hash_table_insert(all_var_hash, name, all_var_list);
}
